7장. 글자를 숫자로 바꾸기

출처: 『AI 엔지니어링 선수지식』(youtubedu 자체 제작 학습노트) | 입문판 재구성 — 노트가 1차 소스(PDF 원본 없음)

코드는 분위기만 — numpy 같은 말은 몰라도 됩니다. 표의 '비유'와 '위험'만 봐도 충분해요.

AI 는 글자를 직접 못 읽습니다.

컴퓨터는 숫자만 압니다.

그래서 "강아지" 같은 글자를 먼저 숫자로 바꿔 줘야 합니다.

이 장은 그 "글자를 숫자로 바꾸는 입구"를 다룹니다.


0. 이 장의 새 단어 (0장에 없는 것만)

0장 용어집에 이미 있는 말(벡터·임베딩·내적·코사인 유사도)은 그대로 가져다 씁니다.

여기서는 0장에 없던 새 단어 3개만 미리 풀어 둡니다.


토큰(token)

한 문장 뜻 — 문장을 쪼갠 의미 조각 하나. AI 가 한 번에 받아들이는 최소 단위입니다.

일상비유 — 레고 한 조각. "안녕하세요"라는 큰 덩어리를 "안녕" + "하세요" 같은 작은 조각으로 나눈 것.

한 줄 예 —

# 문장을 조각으로 나눈 결과 = 토큰 목록
tokens = ["안녕", "하세요"]

위치 표식(position)

한 문장 뜻 — 각 토큰이 문장에서 몇 번째인지 적어 두는 번호.

일상비유 — 극장 좌석 번호. 같은 사람들이 앉아도 자리가 다르면 다른 그림이 됩니다.

한 줄 예 —

# "철수=0번, 좋아해=1번, 영희=2번" 처럼 순서를 번호로 붙임
positions = [0, 1, 2]

정규화(normalize)

한 문장 뜻 — 겉보기엔 같지만 속이 다른 글자를, 하나의 표준 모양으로 통일하는 것.

일상비유 — 같은 "한" 글자를 누구는 도장 한 방으로, 누구는 자음·모음 조각으로 적습니다. 그걸 한 가지 방식으로 맞추는 일.

한 줄 예 —

# 글자를 표준 모양으로 통일
import unicodedata
clean = unicodedata.normalize("NFC", "한글")

(귀납 도입) 이런 적 있죠?

채팅창에 "강아지"라고 입력했습니다.

그런데 AI 안쪽에서는 이 글자를 그대로 쓰지 않습니다.

먼저 조각(토큰)으로 나누고, 각 조각에 번호(ID)를 붙이고, 그 번호를 의미가 담긴 숫자 줄(임베딩)로 바꿉니다.

이 줄을 한 줄로 적으면 이렇습니다.

"문장 → 토큰 → ID → 임베딩"

이 변환이 AI 가 글자를 받아들이는 입구입니다.

이 장에서는 이 입구의 네 부품을 하나씩 봅니다.


이 장에서 딱 4가지만 (TL;DR)

  1. 토큰화·BPE — 문장을 조각으로 쪼개고, 자주 같이 나오는 조각은 한 덩어리로 묶는다.
  2. 임베딩 — 토큰 번호를 "의미가 담긴 숫자 줄"로 바꾼다. 뜻이 비슷하면 방향이 비슷하다.
  3. 위치 표식(위치 임베딩) — 단어가 몇 번째인지 알려 준다. 순서가 바뀌면 뜻도 바뀌니까.
  4. NFC 정규화 — 겉보기 같은 한글을 한 가지 표준 모양으로 통일한다.

한 문장으로 — 글자를 숫자로 바꿔 AI 입에 떠먹여 주는 네 가지 준비입니다.


개념 1 — 토큰화·BPE (문장을 조각으로 쪼개기)

막히는 장면

"안녕하세요"를 AI 에 넣고 싶습니다.

그런데 통째로 한 덩어리로 주면, AI 는 이 긴 글자를 다룰 단위가 없습니다.

그렇다고 글자 하나하나("안","녕","하","세","요")로 쪼개면 조각이 너무 잘아집니다.

적당히 의미 있는 조각으로 나눠야 합니다.

그게 토큰화입니다.

일상비유 — 레고 분해

큰 레고 작품을 조각으로 분해한다고 해 봅시다.

자주 같이 붙어 다니는 조각은 굳이 낱개로 안 떼고 한 덩어리로 둡니다.

"low", "lower", "lowest" 에는 공통으로 "low" 가 들어 있죠.

이 "low" 를 매번 쪼개지 않고 한 조각으로 묶어 둡니다.

이렇게 "자주 같이 나오는 조각을 한 덩어리로 묶는 규칙"이 BPE 입니다.

비유 코드 위험
레고를 조각으로 분해 tokens = ["안녕", "하세요"] 너무 잘게 쪼개면 조각 수가 폭증
자주 쓰는 조각은 한 덩어리 vocab = {"안녕": 0, "하세요": 1} 한글은 영어보다 조각이 많이 나옴

한 문장 정의 — 토큰화는 문장을 의미 조각(토큰)으로 쪼개는 것이고, BPE 는 자주 같이 나오는 조각을 한 덩어리로 묶는 규칙입니다.

각 토큰에는 번호(ID)를 붙입니다.

그 번호 장부가 어휘 사전입니다.

# 어휘 사전: 조각마다 번호를 매겨 둔 장부
vocab = {"안녕": 0, "하세요": 1}
ids = [vocab[t] for t in ["안녕", "하세요"]]
print(ids)   # [0, 1]

예시 폭격

(1) 완성예 — 영어 "low", "lower", "lowest" 를 쪼개면

# 공통 "low" 는 한 조각으로 묶이고, 뒤만 달라짐
print(["low"])          # ['low']
print(["low", "er"])    # ['low', 'er']
print(["low", "est"])   # ['low', 'est']

"low" 가 세 단어에서 모두 같은 한 조각으로 재사용됩니다.

(2) 부분완성 — 빈칸을 채워 보세요.

# "안녕하세요" 를 두 조각으로 나누면?
tokens = ["안녕", "____"]   # 빈칸: 하세요

(3) 독립적용 — "강아지가" 를 직접 조각으로 나눠 보세요.

조각 수가 적을수록 AI 가 한 번에 다루기 편합니다.

미니 시나리오 — 요금이 왜 더 나오지?

같은 내용을 영어로 쓸 때와 한글로 쓸 때, AI 사용 요금이 다릅니다.

요금은 보통 토큰 수로 매겨집니다.

한글은 영어보다 조각이 더 많이 나오는 편입니다.

그래서 같은 뜻이라도 한글이 토큰 수가 많아 비용이 더 들 수 있습니다.

더 깊이 (지금 몰라도 됨)Hugging Face LLM Course


개념 2 — 임베딩 (번호를 의미 숫자 줄로)

막히는 장면

토큰에 번호를 붙였습니다.

"안녕=0, 하세요=1" 처럼요.

그런데 이 번호 0, 1 은 그냥 순서일 뿐, 뜻이 없습니다.

번호 0 과 1 이 가깝다고 "안녕"과 "하세요"가 뜻이 비슷한 것도 아니죠.

뜻을 담은 숫자가 필요합니다.

그래서 번호를 의미가 담긴 숫자 줄(임베딩)로 바꿉니다.

일상비유 — 의미 지도

단어마다 지도 위의 점을 하나씩 찍는다고 해 봅시다.

뜻이 비슷한 단어끼리는 가까이 모입니다.

"강아지"와 "개"는 바로 옆이고, "강아지"와 "자동차"는 멀리 떨어집니다.

이 "의미 좌표"가 임베딩입니다.

비유 코드 위험
단어마다 의미 좌표 dog = np.array([0.9, 0.1, 0.8]) 좌표가 엉터리면 검색·추천이 헛다리
가까우면 비슷한 뜻 dog @ car / ...(코사인 유사도) 차원(칸 수)이 안 맞으면 비교 불가

한 문장 정의 — 임베딩은 토큰 번호를 의미가 담긴 숫자 벡터로 바꾼 것이고, 뜻이 비슷하면 방향(코사인 유사도)이 비슷합니다.

코사인 유사도와 벡터는 0장에 정의돼 있습니다. 두 방향이 같으면 1, 정반대면 -1 이라는 그 값입니다.

예시 폭격

(1) 완성예 — "강아지"와 "자동차"는 멀까?

import numpy as np
from numpy.linalg import norm
dog = np.array([0.9, 0.1, 0.8])
car = np.array([0.1, 0.9, 0.05])
print(round(dog @ car / (norm(dog) * norm(car)), 2))   # 0.4 (멀음)

값이 0.4 로 낮으니 둘은 뜻이 먼 단어입니다.

(2) 부분완성 — "강아지"와 "개"는 가까울까?

# 둘 다 비슷한 방향이면 값이 1 에 가깝게 나옴
# 빈칸을 1 에 가까운 좌표로 채워 보세요
dog = np.array([0.9, 0.1, 0.8])
puppy = np.array([0.88, 0.12, ____])   # 빈칸: 0.79 같은 비슷한 값

(3) 독립적용 — "사과"와 "바나나"의 좌표를 직접 비슷하게 찍어, 값이 1 에 가깝게 나오는지 확인해 보세요.

미니 시나리오 — "의미 검색"이 가능한 이유

검색창에 "강아지 키우기"라고 쳤습니다.

글자가 똑같이 들어 있는 문서만 찾으면 "반려견 입양 가이드"는 못 찾습니다.

하지만 임베딩으로 바꾸면 "강아지"와 "반려견"의 좌표가 가깝습니다.

그래서 글자가 달라도 뜻이 비슷한 문서를 찾아냅니다.

이게 "의미 검색"입니다.

더 깊이 (지금 몰라도 됨)Jay Alammar: Illustrated Transformer


개념 3 — 위치 표식 (몇 번째인지 알려 주기)

막히는 장면

"철수가 영희를 좋아해"

"영희가 철수를 좋아해"

두 문장에 쓰인 단어는 똑같습니다. {철수, 영희, 좋아해}

다른 건 순서뿐입니다.

그런데 단어를 그냥 한 묶음으로만 주면, AI 는 누가 누구를 좋아하는지 구분 못 합니다.

순서 정보가 빠졌기 때문입니다.

그래서 각 단어에 "너는 몇 번째야"라는 위치 표식을 붙입니다.

(이 위치 표식을 숫자 줄에 새겨 넣은 것을 위치 임베딩이라고 부릅니다.)

일상비유 — 극장 좌석 번호

같은 관객 세 명이 극장에 들어갑니다.

자리에 번호가 없으면, 누가 앞줄이고 누가 뒷줄인지 알 수 없습니다.

좌석 번호를 붙이면 "철수=1번 자리, 영희=3번 자리"처럼 자리가 확정됩니다.

단어에도 똑같이 번호를 붙이는 겁니다.

비유 코드 위험
좌석 번호 붙이기 positions = [0, 1, 2] 번호 없으면 순서를 모름
자리로 의미 확정 단어 + 위치를 함께 넣음 순서 뒤바뀌면 뜻이 정반대

한 문장 정의 — 위치 표식은 각 단어가 몇 번째인지 알려 주는 번호이고, 순서가 뜻을 바꾸는 문장을 AI 가 구분하게 해 줍니다.

예시 폭격

(1) 완성예 — 단어에 번호를 붙이면

words = ["철수가", "영희를", "좋아해"]
positions = [0, 1, 2]
print(list(zip(words, positions)))
# [('철수가', 0), ('영희를', 1), ('좋아해', 2)]

이제 "철수가"가 0번(맨 앞)이라는 게 확정됩니다.

(2) 부분완성 — 순서를 뒤집으면 번호는?

words = ["영희가", "철수를", "좋아해"]
positions = [0, 1, ____]   # 빈칸: 2

단어 집합은 같아도, 0번이 "영희가"로 바뀌어 뜻이 달라집니다.

(3) 독립적용 — "나는 너를 봤다"의 각 단어에 0,1,2 번호를 직접 붙여 보세요.

미니 시나리오 — 긴 글도 순서를 안 잃는다

AI 에 아주 긴 글을 넣는다고 해 봅시다.

문장이 길어질수록 "이 단어가 몇 번째였지?"를 놓치기 쉽습니다.

위치 표식을 잘 설계하면, 긴 글에서도 단어 순서를 정확히 유지합니다.

덕분에 긴 글도 앞뒤 순서를 헷갈리지 않고 읽습니다.

더 깊이 (지금 몰라도 됨)Stanford CS224N · Jay Alammar Illustrated Transformer


개념 4 — NFC 정규화 (겉보기 같은 한글 통일하기)

막히는 장면

"한글"이라고 적힌 두 파일이 있습니다.

화면에는 완전히 똑같아 보입니다.

그런데 컴퓨터로 두 글자가 같은지 비교하니 False(다르다)라고 나옵니다.

분명히 같은 글자인데요?

이유는 한글을 저장하는 방식이 두 가지라서입니다.

이 둘을 한 가지 표준 모양으로 맞추는 게 NFC 정규화입니다.

일상비유 — 같은 글자, 다른 조립

"한"이라는 글자를 적는 방법이 두 가지입니다.

하나는 완성된 "한"을 도장 한 방으로 찍는 방식(NFC).

다른 하나는 ㅎ + ㅏ + ㄴ 조각을 따로 적어 조립하는 방식(NFD).

사람 눈엔 똑같지만, 컴퓨터는 둘을 다른 글자로 봅니다.

"NFC 로 통일하자"는 건 "전부 도장 한 방 방식으로 맞추자"는 약속입니다.

비유 코드 위험
도장 한 방(NFC) vs 조각 조립(NFD) unicodedata.normalize("NFC", a) 안 맞추면 같은 글자가 == 에서 False
한 가지 모양으로 통일 비교 전에 NFC 로 정규화 한글 검색·파일명에서 단골 버그

한 문장 정의 — NFC 정규화는 겉보기 같지만 속이 다른 한글을 하나의 표준 모양으로 통일하는 것이고, 한글 비교·검색의 정확도를 지켜 줍니다.

예시 폭격

(1) 완성예 — 정규화 전후 비교

import unicodedata
a = "한글"
b = unicodedata.normalize("NFD", a)        # 조각 조립 방식으로
print(a == b)                              # False (다르다고 나옴)
print(a == unicodedata.normalize("NFC", b))  # True (통일하니 같아짐)

NFC 로 맞추기 전에는 같은 "한글"인데도 다르다고 나옵니다.

(2) 부분완성 — 비교 전에 한 줄만 채우면 버그가 사라집니다.

import unicodedata
def same_word(x, y):
    x = unicodedata.normalize("NFC", x)
    y = unicodedata.____(  "NFC", y)   # 빈칸: normalize
    return x == y

(3) 독립적용 — 검색어와 파일명을 비교하기 전에, 둘 다 NFC 로 정규화하는 함수를 직접 만들어 보세요.

미니 시나리오 — 맥과 윈도우 사이의 단골 버그

맥에서 만든 파일과 윈도우에서 만든 파일이 있습니다.

둘 다 이름이 "보고서"인데, 검색하면 한쪽이 안 잡힙니다.

맥은 조각 조립 방식(NFD), 윈도우는 도장 방식(NFC) 으로 저장하는 경우가 많기 때문입니다.

비교 전에 양쪽을 모두 NFC 로 정규화하면 깔끔하게 해결됩니다.

더 깊이 (지금 몰라도 됨)Hugging Face LLM Course


정리

개념 한 줄 역할
토큰화·BPE 문장을 조각으로 쪼개기 (비용·단위의 기준)
임베딩 조각 번호를 의미 숫자 줄로
위치 표식 단어 순서를 알려 주기
NFC 정규화 겉보기 같은 한글을 한 모양으로 통일

핵심 3줄로 —

AI 는 글자를 직접 못 읽어 숫자로 바꿉니다.

그 흐름이 "문장 → 토큰 → 번호 → 임베딩(+위치)" 입니다.

한글은 비교 전에 NFC 로 통일해야 같은 단어로 처리됩니다.

단순 규칙 — 글자를 다루기 전에 "쪼개고(토큰), 번호 붙이고(ID), 의미 줄로 바꾸고(임베딩), 순서 표시하고(위치), 한글이면 NFC 로 통일한다." 이 순서만 기억하면 됩니다.

한 걸음 더 ▸ (지금 몰라도 됨)

토큰·임베딩·위치 표식은 결국 0장의 세 갈래로 풀립니다. 숫자로 바꾸고(임베딩=벡터), 방향으로 비슷함을 재고(코사인 유사도), 나머지는 곱하고 더하기입니다. 멋진 이름에 겁먹지 않아도 됩니다.

다음 장 예고 — 지금까지 글자를 숫자로 바꾸는 입구를 봤습니다. (지금 몰라도 됩니다 — 다음 장에서 이어집니다.)

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플